#include "../../core/pch.h"
#include "../utils/utils.c"
#include "../../pi/BinarySemaphore.h"

/** Id identifying shared memory segment id. */
int segment_id;

/** Semaphore id which will be used to synchornize master processes. */
int sem_id;

/** Pointer for shared memory. */
struct info_table* shared_memory;

/** Thread specific variables. */
int thread_count = 0, ret;

/** Port on which to listen. */
int logger_port;

void* info_func(void* args)
{
	while (true)
	{
		BinarySemaphore::Decrement(sem_id);
		IT_PRINT();
		BinarySemaphore::Increment(sem_id);
		usleep(250000);
	}
}

void* logger_func(void* args)
{
	DEBUGS("logger: thread started");

	/** Argument to logger_func containing data about connected client. */ 
	struct logger_thread_arg logger_arg = *((struct logger_thread_arg*) args);

	/** Used for protocol messages. */
	char msg;

	/** Holds ip of the client. */
	char *ip;

	/** Port and pid of the client. */
	int port, pid;

	/** Needed for getting current time of day. */
	struct timeval tv;

	/** When we know that client is alive we set this to current time. */
	time_t startTime;

	/** Indicates if connected client is still alive. */
	bool isAlive = false;

	/** Get ip, port and pid of connected client. */
	ip 	 = inet_ntoa(logger_arg.cliaddr.sin_addr);
	port = logger_arg.cliaddr.sin_port;
	pid  = logger_arg.slave_pid;

	/**
	 * Set this socket as non-blocking. We don't have
	 * to use select, because we can call uslepp or 
	 * sleep, although we could use it.
	 */
	fcntl(logger_arg.client_sock, F_SETFL, O_NONBLOCK);

	/** Get current time of day. */
	gettimeofday(&tv, NULL);
	startTime = tv.tv_sec;
	
	while (true)
	{
		gettimeofday(&tv, NULL);

		/** Sleep some time before next read. */
		sleep(TIME_TO_WAIT_FOR_DATA);

		if (recv(logger_arg.client_sock, &msg, sizeof(char), 0) != NO_DATA_TO_READ)
		{
			switch (msg)
			{
				case ALIVE_MSG:
					DEBUGS("ALIVE_MSG");
					isAlive = true;
					startTime = tv.tv_sec;
					break;
				case IM_IDLE:
					DEBUGS("make idle ip = %s, port = %d, pid = %d", ip, port, pid);
					BinarySemaphore::Decrement(sem_id);
					IT_MAKE_IDLE(ip, port, pid);
					BinarySemaphore::Increment(sem_id);
					break;
			}
			msg = NONE;
		}

		if (shared_memory->NUMBER == 1)
			exit(0);

		if (!isAlive && (tv.tv_sec - startTime) > TIMEOUT_FOR_IM_ALIVE)
		{
			BinarySemaphore::Decrement(sem_id);
			IT_MAKE_DEAD(ip, port);
			BinarySemaphore::Increment(sem_id);
			pthread_exit(NULL);
		}

		isAlive = false;
	}
}

int main(int argc, char** argv)
{
	DEBUGS("logger: running");

	segment_id = atoi(argv[1]);
	sem_id = atoi(argv[2]);
	logger_port = atoi(argv[3]);

	/**
	 * Create listening socket for LOGGER process.
	 * It is non-blocking socket.
	 */
	struct sockaddr_in logger_addr; 
	memset(&logger_addr, 0, sizeof(logger_addr)); 
	logger_addr.sin_family = AF_INET; 
	logger_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
	logger_addr.sin_port = htons(logger_port); 

	int logger_sock = socket(AF_INET, SOCK_STREAM, 0); 
	int yes = 1;
	setsockopt(logger_sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	bind(logger_sock, (struct sockaddr*) &logger_addr, sizeof(logger_addr)); 
	listen(logger_sock, 64);

	/**
	 * Attach the shared memory segment.
	 */
	shared_memory = (struct info_table*) shmat(segment_id, NULL, 0);

	/**
	 * Now we can cooperate with SENDER and RECEIVER on
	 * shared memory. Logger will now listen on socket
	 * created by init and accept alive packets from
	 * slaves.
	 */
	struct sockaddr_in cliaddr;
	socklen_t len;

	/**
	 * Thread specific structures.
	 */
	pthread_t logger_thread[MAX_SLAVES];
	pthread_t info_thread;
	struct logger_thread_arg logger_arg[MAX_SLAVES];
	pthread_attr_t attr;

	/**
	 * Make all threads joinable.
	 */
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	pthread_create(&info_thread, NULL, info_func, NULL);

	while (1)
	{
		len = sizeof(struct sockaddr);

		DEBUGS("logger: waiting for connection");

		int client_sock = accept(logger_sock, (struct sockaddr*) &cliaddr, &len);

		DEBUGS("logger: accepted connection");

		pid_t slave_pid;
		int worker_port;
		ret = recv(client_sock, &slave_pid, sizeof(pid_t), 0);
		ret = recv(client_sock, &worker_port, sizeof(int), 0);

		DEBUGS("logger: slaves pid %d", slave_pid);

		/**
		 * For each client connection start a thread which will gather
		 * ALIVE_MSG signals and fill info table.
		 */
		logger_arg[thread_count].client_sock = client_sock;
		logger_arg[thread_count].cliaddr = cliaddr;
		logger_arg[thread_count].slave_pid = slave_pid;

		BinarySemaphore::Decrement(sem_id);
		IT_ADD(inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port, slave_pid, worker_port);
		BinarySemaphore::Increment(sem_id);

		pthread_create(&logger_thread[thread_count], &attr, logger_func, 
			(struct logger_thread_arg*) &logger_arg[thread_count]);

		thread_count++;
	}

	DEBUGS("logger: exited");

	pthread_exit(0);

	return 0;
}

